package org.wupgame.service.impl;

import jakarta.annotation.Resource;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.wupgame.event.GameStateChangedEvent;
import org.wupgame.model.*;
import org.wupgame.model.dto.ActionDTO;
import org.wupgame.model.e.Config;
import org.wupgame.model.e.Direction;
import org.wupgame.model.e.DrawType;
import org.wupgame.repository.ConnectionRepository;
import org.wupgame.repository.RoomRepository;
import org.wupgame.service.GameRule;
import org.wupgame.service.GameService;
import org.wupgame.service.e.GameException;
import org.wupgame.util.RedisLockUtil;
import org.wupgame.util.RedisUtil;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.UUID;

/**
 * 游戏服务实现类 负责处理游戏相关的业务逻辑，包括创建游戏、加入游戏、开始游戏等
 */
@Service
public class GameServiceImpl implements GameService {
	
	// 默认游戏配置
	private static final int MIN_PLAYERS = 2;
	private static final int MAX_PLAYERS = 8;
	private static final int DEFAULT_CREATE_NUMBER = 112;
	@Resource
	private ConnectionRepository connectionRepository;
	@Resource
	private GameRule gameRule;
	@Resource
	private RoomRepository roomRepository;
	@Resource
	private RedisUtil redisUtil;
	@Resource
	private RedisLockUtil redisLockUtil;
	
	@Resource
	private ApplicationEventPublisher eventPublisher;
	
	@Override
	@Transactional
	public RoomState createNewGame(
		Player creator,
		String gameName,
		String password) throws GameException {
		try {
			// 查询创建者是否已经创建其他房间
			getGameState(creator);
			// 创建新的房间状态
			RoomState roomState = new RoomState();
			// 生成唯一的房间ID，使用UUID确保全局唯一性
			roomState.setId(UUID
				                .randomUUID()
				                .toString());
			// 初始化玩家列表，创建者作为第一个玩家加入游戏
			ArrayList<Player> players = new ArrayList<>();
			LinkedHashSet<Card> cards = new LinkedHashSet<>();
			creator.setCards(cards);
			players.add(creator);
			roomState.setPlayers(players);
			// 设置当前回合索引为0，表示第一个玩家（创建者）先开始
			roomState.setTurnIndex(0);
			// 设置当前回合数为0，表示游戏刚刚开始
			roomState.setTurnCount(0);
			// 设置游戏方向为顺时针，这是UNO游戏的默认方向
			roomState.setDirection(Direction.CLOCKWISE);
			// 初始化待抽牌数为0，表示当前没有累积的抽牌惩罚
			roomState.setPendingDraw(0);
			// 设置抽牌类型为无，表示当前没有特殊的抽牌状态
			roomState.setDrawType(DrawType.NONE);
			// 记录最后执行操作的玩家ID，初始为创建者
			roomState.setLastActionPlayer(creator.getId());
			// 默认+4质疑状态设定为否
			roomState.setAwaitingChallenge(false);
			// 上一位出+4的玩家ID设定为无
			roomState.setLastWildDrawPlayer(null);
			// 设置默认游戏配置
			// CHAIN: +4叠加规则，第1张以后的+4不能被质疑
			// IMMEDIATE: 立即出牌规则，例如被加牌后被加牌者能出就出
			// STACKING: 允许累积效果（例如可以叠加+2）
			// CHALLENGE: 允许质疑+4牌的合法性
			ArrayList<Config> configs = new ArrayList<>();
			configs.add(Config.CHAIN);
			configs.add(Config.CHALLENGE);
			configs.add(Config.STACKING);
			roomState.setConfigs(configs);
			// 将房间信息持久化存储
			roomRepository.insertRoom(
				roomState.getId(),
				gameName,
				password,
				MAX_PLAYERS);
			// 房主加入房间
			connectionRepository.insertConnection(
				creator.getId(),
				roomState.getId());
			// 将房间状态存储进Redis
			redisUtil.set(
				roomState.getId(),
				roomState);
			return roomState;
		}
		catch (GameException e) {
			throw new GameException("无法创建房间");
		}
		
	}
	
	@Override
	@Transactional
	public RoomState joinGame(
		String roomId,
		Player player,
		String password) throws GameException {
		// 构建Redis分布式锁的键名，使用房间ID确保每个房间有独立的锁
		String lockKey = "game_lock:" + roomId;
		// 尝试获取分布式锁，防止多个玩家同时加入同一房间导致数据不一致
		// 如果获取锁失败，说明有其他操作正在处理该房间
		if (! redisLockUtil.tryLock(
			lockKey)) {
			throw new GameException("无法获取游戏锁，请稍后再试");
		}
		
		try {
			// 检查房间是否存在
			if (roomRepository
				    .findRoomById(roomId)
			    == null) {
				throw new GameException("房间不存在");
			}
			
			// 密码验证
			if (! Objects.equals(
				roomRepository
					.findRoomById(roomId)
					.getRoomPassword(),
				password)) {
				throw new GameException("密码错误");
			}
			
			// 获取房间状态，从Redis或数据库中读取当前房间的详细信息
			RoomState roomState = getRoomState(roomId);
			
			// 检查玩家是否加入其他房间
			getGameState(player);
			
			// 检查玩家是否已在游戏中，防止同一玩家重复加入
			for (Player p : roomState.getPlayers()) {
				if (p
					.getId()
					.equals(player.getId())) {
					throw new GameException("玩家已在游戏中");
				}
			}
			
			// 检查游戏人数是否已满，确保不超过最大玩家数限制
			if (roomState
				    .getPlayers()
				    .size() >= MAX_PLAYERS) {
				throw new GameException("游戏人数已满");
			}
			
			// 添加玩家到游戏
			LinkedHashSet<Card> cards = new LinkedHashSet<>();
			player.setCards(cards);
			roomState
				.getPlayers()
				.add(player);
			
			// 保存更新后的房间状态到Redis或数据库
			saveRoomState(roomState);
			connectionRepository.insertConnection(
				player.getId(),
				roomId);
			roomRepository.plusPlayerCount(roomId);
			// 返回更新后的房间状态，包含新加入的玩家
			return roomState;
		}
		finally {
			// 无论操作成功还是失败，都必须释放分布式锁
			// 使用finally确保锁一定会被释放，避免死锁
			redisLockUtil.unlock(lockKey);
		}
	}
	
	/**
	 * 开始游戏的方法
	 *
	 * @param roomId 房间ID
	 * @param player 请求开始游戏的玩家
	 *
	 * @return 更新后的房间状态
	 * @throws GameException 如果获取锁失败、非创建者操作或游戏人数不足时抛出
	 */
	@Override
	@Transactional  // 事务注解，确保方法内的数据库操作在事务中执行
	public RoomState startGame(
		String roomId,
		// 房间ID
		Player player) throws GameException {  // 玩家对象，可能抛出游戏异常
		// 使用Redis分布式锁，防止并发问题
		String lockKey = "game_lock:" + roomId;
		
		// 尝试获取锁，如果获取失败则抛出异常
		if (! redisLockUtil.tryLock(lockKey)) {
			throw new GameException("无法获取游戏锁，请稍后再试");
		}
		
		try {
			// 获取房间状态
			RoomState roomState = getRoomState(roomId);
			
			if (roomRepository
				.findRoomById(roomId)
				.isGameStart()) {
				throw new GameException("游戏已经开始");
			}
			
			// 检查权限（只有创建者可以开始游戏）
			if (! roomState
				.getPlayers()
				.getFirst()
				.getId()
				.equals(player.getId())) {
				throw new GameException("只有游戏创建者可以开始游戏");
			}
			
			// 检查游戏人数是否满足最小要求
			if (roomState
				    .getPlayers()
				    .size() < MIN_PLAYERS) {
				throw new GameException("游戏人数不足，无法开始");
			}
			
			// 初始化游戏
			roomState = gameRule.initializeGame(
				roomState,
				DEFAULT_CREATE_NUMBER);
			
			// 保存更新后的房间状态
			saveRoomState(roomState);
			roomRepository.gameStart(roomId);
			// 发布房间状态变更事件，交由 WebSocketHandler 广播
			eventPublisher.publishEvent(new GameStateChangedEvent(
				roomId,
				"gameStarted"));
			
			return roomState;
		}
		finally {
			redisLockUtil.unlock(lockKey);
		}
	}
	
	
	/**
	 * 处理玩家操作的方法
	 *
	 * @param actionDTO 包含房间ID、玩家ID、卡片ID和所选颜色的数据传输对象
	 *
	 * @return RoomState 返回处理后的房间状态
	 * @throws GameException 如果游戏过程中出现异常则抛出
	 */
	@Transactional
	@Override
	public RoomStateMsg processPlayerAction(
		ActionDTO actionDTO) throws GameException {
		try {
			if (actionDTO
				.getCardId()
				.isEmpty()) {
				gameRule.drawCard(
					actionDTO.getRoomId(),
					actionDTO.getPlayerId(),
					actionDTO.isChallenge());
			} else {
				gameRule.playCard(
					actionDTO.getRoomId(),
					actionDTO.getPlayerId(),
					actionDTO.getCardId(),
					actionDTO.getChosenColor(),
					actionDTO.isChallenge());
			}
			eventPublisher.publishEvent(new GameStateChangedEvent(
				actionDTO.getRoomId(),
				"gameStateChanged"));
			return new RoomStateMsg(
				getRoomState(actionDTO.getRoomId()),
				actionDTO.getPlayerId());
		}
		catch (GameException e) {
			throw new GameException(e.getMessage());
		}
		
	}
	
	@Override
	@Transactional
	public RoomState leaveGame(
		String roomId,
		Player player) throws GameException {
		String lockKey = "game_lock:" + roomId;
		if (! redisLockUtil.tryLock(
			lockKey)) {
			throw new GameException("无法获取游戏锁，请稍后再试");
		}
		
		try {
			// 获取房间状态
			RoomState roomState = getRoomState(roomId);
			
			// 检查玩家是否在游戏中
			boolean playerFound = false;
			int playerIndex = - 1;
			for (
				int i = 0;
				i < roomState
					.getPlayers()
					.size();
				i++) {
				if (roomState
					.getPlayers()
					.get(i)
					.getId()
					.equals(player.getId())) {
					playerFound = true;
					playerIndex = i;
					break;
				}
			}
			
			if (! playerFound) {
				throw new GameException("您不在此游戏中");
			}
			
			if (roomState.getTurnIndex() == playerIndex) {
				gameRule.moveToNextPlayer(roomState);
			}
			
			// 从游戏中移除玩家
			roomState
				.getPlayers()
				.remove(playerIndex);
			connectionRepository.deleteConnection(
				player.getId(),
				roomId);
			roomRepository.reducePlayerCount(roomId);
			
			
			// 如果是创建者离开，需要转移创建者身份
			if (playerIndex == 0 && ! roomState
				.getPlayers()
				.isEmpty()) {
				// 第一个玩家成为新的创建者
				// 这里不需要做特殊处理，因为玩家数组已经更新
			}
			
			if (roomState
				.getPlayers()
				.isEmpty()) {
				// 如果没有其他玩家，删除游戏
				deleteRoomState(roomId);
				return null;
			}
			
			// roomState.setPlayers(newPlayers);
			
			// 如果游戏正在进行中且玩家数量低于最小要求，结束游戏
			// 这里可以根据需要添加游戏结束的逻辑
			
			// 保存更新后的房间状态
			saveRoomState(roomState);
			
			return roomState;
		}
		finally {
			redisLockUtil.unlock(lockKey);
		}
	}
	
	
	private void getGameState(
		Player player) throws GameException {
		// 获取房间状态
		Room room = connectionRepository.findConnectionByPlayerId(player.getId());
		if (room != null) {
			throw new GameException("您已加入其他房间！");
		}
	}
	
	@Override
	public Page<Room> getAvailableGames(int pageNum) {
		// 这里需要实现获取所有等待中游戏的逻辑
		Pageable pageable = PageRequest.of(
			pageNum,
			8);
		return roomRepository.findAllNotStartRoom(pageable);
	}
	
	@Override
	public RoomState getPlayerGames(Player player) {
		Room room = connectionRepository.findConnectionByPlayerId(player.getId());
		if (room != null) {
			return getRoomState(room.getId());
		} else {
			throw new GameException("玩家没有加入任何游戏！");
		}
	}
	
	/**
	 * 获取房间状态
	 *
	 * @param roomId 房间ID
	 *
	 * @return 房间状态
	 * @throws GameException 如果房间不存在
	 */
	private RoomState getRoomState(String roomId) throws GameException {
		// 这里应该从Redis或数据库中获取房间状态
		try {
			return (RoomState) redisUtil.get(roomId);
		}
		catch (Exception e) {
			throw new GameException("房间不存在");
		}
		// 如果房间不存在，抛出异常
		// throw new GameException("房间不存在");
	}
	
	/**
	 * 保存房间状态
	 *
	 * @param roomState 房间状态
	 */
	private void saveRoomState(RoomState roomState) {
		// 这里应该将房间状态保存到Redis或数据库
		// 由于没有具体的存储实现，这里不做任何操作
		// 实际实现需要根据存储方式进行调整
		
		redisUtil.set(
			roomState.getId(),
			roomState);
	}
	
	/**
	 * 删除房间状态
	 *
	 * @param roomId 房间ID
	 */
	private void deleteRoomState(String roomId) {
		// 这里应该从Redis或数据库中删除房间状态
		// 由于没有具体的存储实现，这里不做任何操作
		// 实际实现需要根据存储方式进行调整
		redisUtil.del(roomId);
		roomRepository.deleteRoom(roomId);
		connectionRepository.deleteAllConnectionsByRoomId(roomId);
	}
}